/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package uk.ac.rdg.resc.mmd.controllers;

import org.jdom.JDOMException;
import com.connexience.client.api.impl.HttpClientAPI;
import com.connexience.server.api.APIConnectException;
import com.connexience.server.api.APIFactory;
import com.connexience.server.api.APIInstantiationException;
import com.connexience.server.api.APIParseException;
import com.connexience.server.api.APISecurityException;
import com.connexience.server.api.IDocument;
import com.connexience.server.api.IFolder;
import com.connexience.server.api.IObject;
import com.connexience.server.api.IUser;
import com.connexience.server.api.IWorkflow;
import com.connexience.server.api.IWorkflowInvocation;
import com.connexience.server.api.impl.InkspotDocument;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
import uk.ac.rdg.resc.mmd.colocation.NcModelTimeseries;
import uk.ac.rdg.resc.mmd.ncwms.exceptions.InvalidDimensionValueException;
import uk.ac.rdg.resc.mmd.security.SecurityUtils;

/**
 *
 * @author Alastair
 */
public class MMDController extends AbstractController {

    //should be obtained from deployment descriptor
    private static final String AppID = "43e14ff52665a49301267a25228b5568";        // peter.morris@esc.com account
    //private static final String AppID = "43e14ff526b7e9260126b8914f181178";      // alg@mail.nerc-essc.ac.uk account
    private static final String AppKey = "7476318ceb284f329ec44931415b2df1";       // peter.morris@esc.com account
    //private static final String AppKey = "07415d3dff0130cf5fac663dea59d4f2";     // alg@mail.nerc-essc.ac.uk account
    private static final String eScienceURL = "http://esc.selfip.com/APIServer";

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

        String userRequest = request.getParameter("request");

        if (userRequest.equals("showFilesAndWorkflows"))
            return showFilesAndWorkflows(request);
        else if (userRequest.equals("Run Workflow"))
            return runWorkflow(request);
        else if (userRequest.equals("List Workflow Outputs"))
            return listWorkflowOutputs(request);
        else if (userRequest.equals("showOutput"))
            return showOutput(request, response);
        else if (userRequest.equals("upload"))
            return uploadDocument(request);
        else if (userRequest.equals("Delete File"))
            return deleteFile(request);
        else if (userRequest.equals("Download File"))
            return downloadFile(request, response);
        else if (userRequest.equals("Run DIVA Interpolation. Get Map Image"))
            return getDivaMapImage(request, response);
        else if (userRequest.equals("Run DIVA Interpolation. Get Netcdf of field"))
            return getDivaNetcdf(request);
        else if (userRequest.equals("Show Valid Model Depths at Obs Location"))
            return showModelDepths(request);
        else if (userRequest.equals("Show Model - Obs Timeseries Plot")) 
            return showModelObsTimeseries(request, response);
        else if (userRequest.equals("Contour Depth-Time Plot"))
            return getContourPlot(request, response);
        else if (userRequest.equals("Filled Block Depth-Time Plot"))
            return getFilledBlockPlot(request, response);
        else if (userRequest.equals("Create and Save Profile Timeseries CSV")) 
            return getProfileTimeseriesCsv(request, response);
        else if (userRequest.equals("Generate KML (e.g for Google Earth)"))
            return getKml(request, response);
        else if (userRequest.equals("Flot Model - Obs Timeseries Plot"))
            return showModelObsFlotTimeseries(response, request);
        else
            throw new Exception("request parameter not recognised");

    }

    private static ModelAndView showFilesAndWorkflows(HttpServletRequest request) throws MalformedURLException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException, FileNotFoundException {

        //create an instance of the API client lib
        APIFactory factory = new APIFactory();
        factory.setApiClass(com.connexience.client.api.impl.HttpClientAPI.class);

        //authenticate the application - this checks that the application is valid on this server
        HttpClientAPI api = (HttpClientAPI) factory.authenticateApplication(new URL(eScienceURL), AppID, AppKey);

        //authenticate the user of the application - this enables users to give applications different priviledges
        IUser user = api.authenticate(SecurityUtils.getCurrentUsername(), SecurityUtils.getCurrentPassword());

        request.getSession().setAttribute("esc_api", api);
        
        Map<String, Object> models = new HashMap<String, Object>();
        
        List<IWorkflow> listWorkflows = api.listWorkflows();
        Map<String, String> workflows = new HashMap<String, String>(listWorkflows.size());
        
        for (IWorkflow workflow : listWorkflows) {
            workflows.put(workflow.getName(), workflow.getId());
        }

        IFolder homeFolder = api.getUserFolder(user);
        List<IObject> contents = api.getFolderContents(homeFolder);
        Map<String, String> documents = new HashMap<String, String>(contents.size());
        
        for (IObject iobject : contents) {
            if (iobject instanceof IDocument) {
                IDocument doc = (IDocument) iobject;
                documents.put(doc.getName(), doc.getId());
            }
        }

        models.put("userName", user.getName());
        models.put("workflows", workflows);
        models.put("documents", documents);

        return new ModelAndView("showFilesAndWorkflows", models);
    }

    private static ModelAndView runWorkflow(HttpServletRequest request) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, Exception {

        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");

        //get the file to run through the workflow
        IDocument doc = api.getDocument(request.getParameter("file"));

        //find the workflow we're going to run.  This method is very clunky, we'll provide a direct lookup for the workflow
        List<IWorkflow> workflows = api.listWorkflows();
        IWorkflow workflowToRun = null;
        for (IWorkflow workflow : workflows) {
            if (workflow.getId().equals(request.getParameter("workflow"))) {
                workflowToRun = workflow;
                break;
            }
        }

        //run the workflow
        if (workflowToRun != null) {

            IWorkflowInvocation inv = api.executeWorkflow(workflowToRun, doc);
            Map<String, Object> models = new HashMap<String, Object>();

            models.put("workflowName", workflowToRun.getName());
            models.put("docName", doc.getName());
            models.put("invId", inv.getId());
            models.put("invocationId", inv.getInvocationId());

            return new ModelAndView("workflowStatus", models);
        }
        else
            throw new Exception("could not find workflow to run");
    }

    private static ModelAndView listWorkflowOutputs(HttpServletRequest request) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException {

        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");

        //get the status of the workflow
        String invocationId = request.getParameter("invocationId");
        IWorkflowInvocation inv = api.getWorkflowInvocation(invocationId);

        Map<String, Object> models = new HashMap<String, Object>();
        models.put("invStatus", inv.getStatus());
        models.put("invocationID", invocationId);

        Map<String, String> documents = new HashMap<String, String>();

        //get the results of the workflow.
        //workflow runs output the results to a folder so we can get the contents of that folder
        List<IObject> contents = api.getFolderContents(inv);
        for (IObject iobject : contents) {
            if (iobject instanceof IDocument) {
                IDocument doc = (IDocument) iobject;
                documents.put(doc.getName(), doc.getId());
            }
        }
        models.put("documents", documents);

        return new ModelAndView("listWorkflowOutputs", models);
    }

    private static ModelAndView showOutput(HttpServletRequest request, HttpServletResponse response) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException {
        
        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");

        IWorkflowInvocation inv = api.getWorkflowInvocation(request.getParameter("invocationId"));
        List<IObject> contents = api.getFolderContents(inv);
        for (IObject iobject : contents) {
            if (iobject instanceof IDocument) {
                IDocument doc = (IDocument) iobject;

                if (doc.getId().equals(request.getParameter("docId"))) {
                    if (doc.getName().endsWith(".jpeg")) {
                        response.setContentType("image/jpg");
                    } else if (doc.getName().endsWith(".xml")) {
                        response.setContentType("text/xml");
                    } else if (doc.getName().endsWith(".html")) {
                        response.setContentType("text/html");
                    }
                    api.download(doc, response.getOutputStream());
                }
            }
        }
        return new ModelAndView();
    }

    /* Use Apache Commons to get an input stream for the uploaded file itself (ignoring form upload metadata),
       and then pass this to the e-Science Central api upload() method to actually upload the file */
    private static ModelAndView uploadDocument(HttpServletRequest request) throws FileNotFoundException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, FileUploadException {

        boolean isMultipart = ServletFileUpload.isMultipartContent(request);

        if (isMultipart) {
            // Create a factory for disk-based file items
            FileItemFactory factory = new DiskFileItemFactory();

            // Create a new file upload handler
            ServletFileUpload upload = new ServletFileUpload(factory);

            // Parse the request. We can suppress the unchecked cast warning here
            // because looking at the apache commons code reveals that it DOES
            // return a List of FileItems.
            @SuppressWarnings("unchecked")
            List<FileItem> items = (List<FileItem>)upload.parseRequest(request);

            // Process the uploaded items
            for (FileItem item : items) {

                // only interested in processing the actual file content, not the form data
                if (!item.isFormField() && item.getSize() > 0) {
                    InputStream uploadedStream = item.getInputStream();
                    String fileName = item.getName();

                    HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");
                    uploadDocToESC(fileName, uploadedStream, request);
                }
            }
        }

        // We now want to update the display of workflows and data files, so we
        // run 'showFilesAndWorkflows' and return that ModelAndView
        return showFilesAndWorkflows(request);
    }

    private static ModelAndView deleteFile(HttpServletRequest request) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, MalformedURLException, FileNotFoundException {
        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");
        
        IDocument doc = api.getDocument(request.getParameter("file"));
        api.deleteDocument(doc);

        // We now want to update the display of workflows and data files, so we
        // run 'showFilesAndWorkflows' and return that ModelAndView
        return showFilesAndWorkflows(request);
    }

    private static ModelAndView downloadFile(HttpServletRequest request, HttpServletResponse response) throws MalformedURLException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException, FileNotFoundException, IOException {
        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");
        IDocument doc = api.getDocument(request.getParameter("file"));
        
        if (doc.getName().endsWith(".nc"))
            response.setContentType("application/x-netcdf");
        else if (doc.getName().endsWith(".csv"))
            response.setContentType("application/excel");
        else if (doc.getName().endsWith(".txt"))
            response.setContentType("text/plain");
        response.setHeader("Content-Disposition","inline; filename=" + doc.getName());
        
        api.download(doc, response.getOutputStream());
        return new ModelAndView();
    }

    private static ModelAndView getDivaMapImage(HttpServletRequest request, HttpServletResponse response) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, JDOMException {
        // Download requested file from e-SC
        ByteArrayOutputStream escOut = new ByteArrayOutputStream();
        downloadDocFromEsc(request, request.getParameter("file"), escOut);

        // Send output from e-SC as input to DIVA for interpolation and pull back resulting WMS image
        DivaController divaController = new DivaController();
        InputStream wmsStream = divaController.getInterpolatedMapImage(new ByteArrayInputStream(escOut.toByteArray()));
        
        // Stream the WMS output from DIVA to the browser
        streamDataToResponse(wmsStream, response);

        return new ModelAndView();
    }

    private static ModelAndView getDivaNetcdf(HttpServletRequest request) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, JDOMException {
        // Download requested file from e-SC
        ByteArrayOutputStream escOut = new ByteArrayOutputStream();
        downloadDocFromEsc(request, request.getParameter("file"), escOut);

        // Send output from e-SC as input to DIVA for interpolation and pull back resulting NetCDF as InputStream
        DivaController divaController = new DivaController();
        InputStream netcdfStream = divaController.getInterpolatedNetcdf(new ByteArrayInputStream(escOut.toByteArray()));

        // upload the NetCDF output from DIVA to e-SC
        uploadDocToESC("diva_output.nc", netcdfStream, request);

        // We now want to update the display of workflows and data files, so we
        // run 'showFilesAndWorkflows' and return that ModelAndView
        return showFilesAndWorkflows(request);
    }

    // Show the user the available model depths so they can select which depth
    // they want for the model when used in producing the model-obs timeseries
    private static ModelAndView showModelDepths(HttpServletRequest request) throws FileNotFoundException, IOException, InvalidDimensionValueException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException {
        String file = request.getParameter("file");
        String modelLocation = request.getParameter("model");
        ColocationController colocationController = getColocationController(request, file, modelLocation);
        
        // Retrieve depths and units for jsp page display
        List<Double> elevations = colocationController.getModelElevations();
        String elevationUnits = colocationController.getModelElevationUnits();

        // Store depths info in an MVC model Map and output jsp page with this info
        Map<String, Object> MVCModels = new HashMap<String, Object>();
        MVCModels.put("elevations", elevations);
        MVCModels.put("elevationUnits", elevationUnits);

        return new ModelAndView("showModelDepths", MVCModels);
    }

    // Now that user has chosen the model depth, we show timeseries at this depth
    private static ModelAndView showModelObsTimeseries(HttpServletRequest request, HttpServletResponse response) throws InvalidDimensionValueException, IOException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException, Exception {
        String file = request.getParameter("file");
        String modelLocation = request.getParameter("model");
        ColocationController colocationController = getColocationController(request, file, modelLocation);

        String elevationStr = request.getParameter("elevation");
        double elevation = elevationStr.equals("") ?
            colocationController.getModelElevation() : Double.parseDouble(elevationStr);
        response.setContentType("image/png");
        colocationController.streamTimeseriesChart(response.getOutputStream(), elevation);

        return null;
    }

    private static ModelAndView showModelObsFlotTimeseries(HttpServletResponse response, HttpServletRequest request) throws IOException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException, FileNotFoundException, InvalidDimensionValueException, Exception {

        String file = request.getParameter("file");
        String modelLocation = request.getParameter("model");
        ColocationController colocationController = getColocationController(request, file, modelLocation);

        String elevationStr = request.getParameter("elevation");
        double elevation = elevationStr.equals("") ?
            colocationController.getModelElevation() : Double.parseDouble(elevationStr);
        response.setContentType("text/html");
        colocationController.streamFlotTimeseriesChart(response.getOutputStream(), elevation);

        return null;
    }

    private static ModelAndView getContourPlot(HttpServletRequest request, HttpServletResponse response) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, FileNotFoundException, InvalidDimensionValueException {
        String file = request.getParameter("file");
        String modelLocation = request.getParameter("model");
        ColocationController colocationController = getColocationController(request, file, modelLocation);

        response.setContentType("image/png");
        colocationController.streamContourPlot(response.getOutputStream(),
                request.getParameter("deepestContourDepth"));

        return null;
    }

    private static ModelAndView getFilledBlockPlot(HttpServletRequest request, HttpServletResponse response) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, FileNotFoundException, InvalidDimensionValueException {
        String file = request.getParameter("file");
        String modelLocation = request.getParameter("model");
        ColocationController colocationController = getColocationController(request, file, modelLocation);
        
        response.setContentType("image/png");
        colocationController.streamFilledBlockPlot(response.getOutputStream(),
                request.getParameter("deepestBlockDepth"));

        return null;
    }

    private ModelAndView getProfileTimeseriesCsv(HttpServletRequest request, HttpServletResponse response) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, FileNotFoundException, InvalidDimensionValueException {

        String file = request.getParameter("file");
        String modelLocation = request.getParameter("model");
        ColocationController colocationController = getColocationController(request, file, modelLocation);

        NcModelTimeseries ncModelTimeseries = colocationController.getNcModelTimeseries();
        List<List<Float>> profileTimeseriesValues = ncModelTimeseries.getProfileTimeseriesValues();

        List<Double> elevations = colocationController.getModelElevations();
        
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(baos);

        // Create CSV format for plot of timeseries at each depth
        for (Double elevation : elevations) {
            ps.print(elevation + ",");
        }
        ps.print("\n");
        for (List<Float> profile : profileTimeseriesValues) {
            for (Float val : profile) {
                ps.print(val + ",");
            }
            ps.print("\n");
        }

        uploadDocToESC("timeTempDepthPlotTest.csv", new ByteArrayInputStream(baos.toByteArray()), request);
        return showFilesAndWorkflows(request);
    }

    private static ModelAndView getKml(HttpServletRequest request, HttpServletResponse response) throws IOException, APIConnectException, APISecurityException, APIParseException, APIInstantiationException, FileNotFoundException, InvalidDimensionValueException {

        String modelLocation = request.getParameter("model");
        String[] files = request.getParameterValues("file");

        StringBuffer kmlStrBuf = new StringBuffer();
        kmlStrBuf.append(KmlController.getHeader());

        for (String file : files) {
            ColocationController colocationController = getColocationController(request, file, modelLocation);
            String kmlBody = colocationController.getKmlBody(file, modelLocation);
            kmlStrBuf.append(kmlBody);
        }
        kmlStrBuf.append(KmlController.getFooter());

        response.setContentType("application/vnd.google-earth.kml+xml");
        response.setHeader("Content-Disposition","inline; filename=MMD_KML_output.kml");
        PrintWriter out = new PrintWriter(response.getOutputStream());
        out.print(kmlStrBuf);
        out.flush();
        out.close();

        return null;
    }


    /**************************************************
    * METHODS NOT CALLED DIRECTLY FROM USER INTERFACE *
    ***************************************************/

    private static ColocationController getColocationController(HttpServletRequest request, 
            String file, String modelLocation) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException, IOException, FileNotFoundException, InvalidDimensionValueException {
        // Download the obs file from e-SC and use as input for obs-model co-location
        ByteArrayOutputStream obsOutOfEsc = new ByteArrayOutputStream();
        downloadDocFromEsc(request, file, obsOutOfEsc);
        ByteArrayInputStream obsInToColocation = new ByteArrayInputStream(obsOutOfEsc.toByteArray());

        // Get model URL/File location from request and create Colocation contreoller
        // using obs file inputstream.
        return new ColocationController(obsInToColocation, modelLocation);
    }

    private static void downloadDocFromEsc(HttpServletRequest request, String fileName, OutputStream out) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException {
        // Get the document from the e-Science Central API
        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");
        IDocument doc = api.getDocument(fileName);
        api.download(doc, out);
    }

    private static void uploadDocToESC(String filename, InputStream inputStream, HttpServletRequest request) throws APIConnectException, APISecurityException, APIParseException, APIInstantiationException {
        /* to upload a file using e-Science Central api, we need to create a new
           InkspotDocument, give it a name, then save it, and then upload to inputStream */
        HttpClientAPI api = (HttpClientAPI) request.getSession().getAttribute("esc_api");
        IUser user = api.authenticate(SecurityUtils.getCurrentUsername(), SecurityUtils.getCurrentPassword());
        IFolder userFolder = api.getUserFolder(user);
        InkspotDocument newDoc = new InkspotDocument();
        newDoc.setName(filename);
        IDocument savedDocument = api.saveDocument(userFolder, newDoc);
        api.upload(savedDocument, inputStream);
    }

    private static void streamDataToResponse(InputStream streamIn, HttpServletResponse response) throws IOException {
        byte[] buf = new byte[32 * 1024]; // 32k buffer
        int nRead = 0;
        while( (nRead = streamIn.read(buf)) != -1 ) {
            response.getOutputStream().write(buf, 0, nRead);
        }
        response.getOutputStream().flush();
        response.getOutputStream().close();
    }
}
